<?php
/**
 * object instance storage tools interface
 * + flat files base class implementation
 *
 * This file is part of Webappkit, a packages system for PHP web apps
 * @link http://webappkit.net
 * @package webappkit
 * @subpackage storages
 * @author J.Ducastel <nospam0@ducastel.name>
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 */

/**
* Base class for webappkit object instances storages.
* generic filesystem-based storage.
* Use search patterns as path template to retrieve a file by search criteria,
* then build an object instance from the file's content.
* Search patterns may contain strings between double accolads, including an optional criteria name as below
* {optional-part{criteria}optional part}
* If criteria has a value, it will be replaced by its value surrounded by optional parts and accolads will be removed
* Else, the wole string will be removed from search pattern
* @author J.Ducastel <jeremie@ducastel.name>
* @todo criteria validation (regex ?)
*/
class WakStorage extends wakTool implements iWakStorage {
	/**
	 * @var array default configuration to be overrided
	 * [storage][search] => $this->search search pattern
	 * [storage][path]
	 * [storage][class] => $this->stored_class stored objects class
	 * [storage][id_member] object's unique key member
	 * [criteria] => $this->criteria
	 */
	public static $default_cfg=array(
		'storage'=>array(
			'filenames'=>'{{id}}.txt'
			,'class'=>'' // stored objects class
			,'id_member'=>'id' //
		),'criteria'=>array() // default values for search criteria
	);

	/**
	 * factory pattern, use this method to build instances
	 * all subclasses have to implement this method
	 * because of PHP object model limitations
	 * @static
	 * @param string $kit_id parent kit id
	 * @param array $cfg configuration data, will be merged with defaults
	 * @return wakTool
	 */
	public static function toolFactory($kit_id,$cfg=array()) {
		return new WakStorage($kit_id,$cfg);
	}

	public function get($id) {
        // search file from pattern
        $file=$this->_makeFileName($id); // echo "<p>file = $file</p>";
        if (!$file or !file_exists($this->getPath().$file))
            return false;
        // reading file content
        $content=file_get_contents($this->getPath().$file); // echo $content;
        // building instance
        $instance=unserialize($content);
        return $instance;
	}

	/**
	 * search for an item
	 * @access public
	 * @param array $criteria
	 * @return bool
	 */
	public function has($id) {
        $file=$this->getPath().$this->_makeFileName($id); //echo "<p>file = $file</p>";
		return file_exists($file);
	}
	/**
	 * alias for hasItem
	 */
	public function hasItem($id) {
		return $this->has($id);
	}

	public function search($criteria) {

	}

	public function save(&$instance) {
        // building file name
        $key=$this->getIdMember();
        $id=$instance->$key; // echo $id;
        $file=$this->getPath().$this->_makeFileName($id);
        // serialazing for storage
        $serialized=serialize($instance);
        // deleting then saving
        if (file_exists($file) and !unlink($file))
            return false;
        return file_put_contents($file,$serialized);
	}

	public function delete($id) {
        // building file name
        $file=$this->getPath().$this->_makeFileName($id);
        // deleting then saving
        return unlink($file);
	}

	public function getClass() {
		return $this->cfg['storage']['class'];
	}

	public function getIdMember() {
		return $this->cfg['storage']['id_member'];
	}

    public function getPath() {
        $path=Webappkit::getPathFromId($this->getKitId());
        return $path;
    }

	/**
	* sets new default value for a criteria
	* @param string $key
	* @param string $value
	* @access public
	* @return bool
	*/
	public function setCriterium($key,$value) {
		$this->cfg['criteria'][$key]=$value;
		return true;
	}/**/

    public function setCriteria($key,$value) {
        return $this->setCriterium($key,$value);
    }

	/**
	* get search pattern
	* @access public
	* @return string
	*/
	public function getSearchPattern() {
		return $this->cfg['storage']['filenames'];
	}

	/**
	* set search pattern
	* @access public
	* @param string $pattern
	* @return bool
	*/
	public function setSearchPattern($pattern) {
		if (!is_string($pattern))
			return false;
		$this->cfg['storage']['filenames']=$pattern;
		return true;
	}
/*--------------------------------------------------------------------
VARS / MEMBERS
--------------------------------------------------------------------*/


	/**
	 * Merge passed search criteria with defaults
	 * @access protected
	 * @param array $criteria associative array
	 * @return array
	 */
	function _fillCriteria($criteria=array()) {
		// string criteria = id
		/*if (is_string($criteria)) {
			$criteria=array('id'=>$criteria);
		}*/
		// looping defaults
		foreach ($this->cfg['criteria'] as $key => $value) {
			// setting default if not set in arg
			if (!isset($criteria[$key]))
				$criteria[$key]=$value;
		}
		return $criteria;
	}/**/

	/**
	 * injects criteria into search template to get filename.
	 * Replace var's key by their actual value within search template.
	 * Scheme is {optional part before{key}optional part after}
	 * checks that file exists.
	 * @access protected
	 * @param string $id
	 * @return string filename or false if not found
	 */
	function _makeFileName($id) {
        // getting full criteria
        $criteria=$this->_fillCriteria(array('id'=>$id)); // print_r($criteria);
		$n=0;
		$regex='\{([^{}]*)\{([a-zA-Z][a-zA-Z0-9_\.-]*)\}([^{}]*)\}';
		// echo $regex;
		$filename=$this->getSearchPattern();
		while (ereg($regex,$filename,$found)) {
			// avoiding infinite loop
			$n++; if ($n>50) break;
			// print_r($found);
			$before=$found[1];
			$key=$found[2];
			//$force=$found[3];
			$after=$found[3];
			// building replacement block
			if (isset($criteria[$key])
			/*and is_string($criteria[$key])*/
			and $criteria[$key]) {
				// provided, not empty string value
				$replace=$before.$criteria[$key].$after;
			} else {
				// not provided or null value
				$replace='';
			}
			// replacing
			// escaping regex special chars
			$before=str_replace('(','\(',$before);
			$before=str_replace(')','\)',$before);
			$after=str_replace('(','\(',$after);
			$after=str_replace(')','\)',$after);
			// replacing regex
			$ereg2='\{'.$before.'\{'.$key.'\}'.$after.'\}';
			$filename=ereg_replace($ereg2,$replace,$filename);
		}
		// adding kit path
		return $filename;
	}/**/
}
